package net.simonvt.menudrawer;

import net.simonvt.menudrawer.compat.ActionBarHelper;
import net.simonvt.menudrawer.samples.R;

import android.app.Activity;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.os.Build;
import android.os.Bundle;
import android.os.Parcel;
import android.os.Parcelable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.view.ViewTreeObserver;
import android.view.animation.AccelerateInterpolator;
import android.view.animation.Interpolator;

public abstract class MenuDrawer extends ViewGroup
{

	/**
	 * Callback interface for changing state of the drawer.
	 */
	public interface OnDrawerStateChangeListener
	{

		/**
		 * Called when the drawer state changes.
		 *
		 * @param oldState The old drawer state.
		 * @param newState The new drawer state.
		 */
		void onDrawerStateChange(int oldState, int newState);

		/**
		 * Called when the drawer slides.
		 *
		 * @param openRatio    Ratio for how open the menu is.
		 * @param offsetPixels Current offset of the menu in pixels.
		 */
		void onDrawerSlide(float openRatio, int offsetPixels);
	}

	/**
	 * Callback that is invoked when the drawer is in the process of deciding whether it should intercept the touch
	 * event. This lets the listener decide if the pointer is on a view that would disallow dragging of the drawer.
	 * This is only called when the touch mode is {@link #TOUCH_MODE_FULLSCREEN}.
	 */
	public interface OnInterceptMoveEventListener
	{

		/**
		 * Called for each child the pointer i on when the drawer is deciding whether to intercept the touch event.
		 *
		 * @param v     View to test for draggability
		 * @param delta Delta drag in pixels
		 * @param x     X coordinate of the active touch point
		 * @param y     Y coordinate of the active touch point
		 * @return true if view is draggable by delta dx.
		 */
		boolean isViewDraggable(View v, int delta, int x, int y);
	}

	public enum Type {
		/**
		 * Positions the drawer behind the content.
		 */
		BEHIND,

		/**
		 * A static drawer that can not be dragged.
		 */
		STATIC,

		/**
		 * Positions the drawer on top of the content.
		 */
		OVERLAY,
	}

	/**
	 * Tag used when logging.
	 */
	private static final String TAG = "MenuDrawer";

	/**
	 * Indicates whether debug code should be enabled.
	 */
	private static final boolean DEBUG = false;

	/**
	 * The time between each frame when animating the drawer.
	 */
	protected static final int ANIMATION_DELAY = 1000 / 60;

	/**
	 * The default touch bezel size of the drawer in dp.
	 */
	private static final int DEFAULT_DRAG_BEZEL_DP = 24;

	/**
	 * The default drop shadow size in dp.
	 */
	private static final int DEFAULT_DROP_SHADOW_DP = 6;

	/**
	 * Drag mode for sliding only the content view.
	 */
	public static final int MENU_DRAG_CONTENT = 0;

	/**
	 * Drag mode for sliding the entire window.
	 */
	public static final int MENU_DRAG_WINDOW = 1;

	/**
	 * Disallow opening the drawer by dragging the screen.
	 */
	public static final int TOUCH_MODE_NONE = 0;

	/**
	 * Allow opening drawer only by dragging on the edge of the screen.
	 */
	public static final int TOUCH_MODE_BEZEL = 1;

	/**
	 * Allow opening drawer by dragging anywhere on the screen.
	 */
	public static final int TOUCH_MODE_FULLSCREEN = 2;

	/**
	 * Indicates that the drawer is currently closed.
	 */
	public static final int STATE_CLOSED = 0;

	/**
	 * Indicates that the drawer is currently closing.
	 */
	public static final int STATE_CLOSING = 1;

	/**
	 * Indicates that the drawer is currently being dragged by the user.
	 */
	public static final int STATE_DRAGGING = 2;

	/**
	 * Indicates that the drawer is currently opening.
	 */
	public static final int STATE_OPENING = 4;

	/**
	 * Indicates that the drawer is currently open.
	 */
	public static final int STATE_OPEN = 8;

	/**
	 * Indicates whether to use {@link View#setTranslationX(float)} when positioning views.
	 */
	static final boolean USE_TRANSLATIONS = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH;

	/**
	 * Time to animate the indicator to the new active view.
	 */
	static final int INDICATOR_ANIM_DURATION = 800;

	/**
	 * The maximum animation duration.
	 */
	private static final int DEFAULT_ANIMATION_DURATION = 600;

	/**
	 * Interpolator used when animating the drawer open/closed.
	 */
	protected static final Interpolator SMOOTH_INTERPOLATOR = new SmoothInterpolator();

	/**
	 * Interpolator used for stretching/retracting the active indicator.
	 */
	protected static final Interpolator INDICATOR_INTERPOLATOR = new AccelerateInterpolator();

	/**
	 * Drawable used as menu overlay.
	 */
	protected Drawable mMenuOverlay;

	/**
	 * Defines whether the drop shadow is enabled.
	 */
	protected boolean mDropShadowEnabled;

	/**
	 * The color of the drop shadow.
	 */
	protected int mDropShadowColor;

	/**
	 * Drawable used as content drop shadow onto the menu.
	 */
	protected Drawable mDropShadowDrawable;

	private boolean mCustomDropShadow;

	/**
	 * The size of the content drop shadow.
	 */
	protected int mDropShadowSize;

	/**
	 * Bitmap used to indicate the active view.
	 */
	protected Bitmap mActiveIndicator;

	/**
	 * The currently active view.
	 */
	protected View mActiveView;

	/**
	 * Position of the active view. This is compared to View#getTag(R.id.mdActiveViewPosition) when drawing the
	 * indicator.
	 */
	protected int mActivePosition;

	/**
	 * Whether the indicator should be animated between positions.
	 */
	private boolean mAllowIndicatorAnimation;

	/**
	 * Used when reading the position of the active view.
	 */
	protected final Rect mActiveRect = new Rect();

	/**
	 * Temporary {@link Rect} used for deciding whether the view should be invalidated so the indicator can be redrawn.
	 */
	private final Rect mTempRect = new Rect();

	/**
	 * The custom menu view set by the user.
	 */
	private View mMenuView;

	/**
	 * The parent of the menu view.
	 */
	protected BuildLayerFrameLayout mMenuContainer;

	/**
	 * The parent of the content view.
	 */
	protected BuildLayerFrameLayout mContentContainer;

	/**
	 * The size of the menu (width or height depending on the gravity).
	 */
	protected int mMenuSize;

	/**
	 * Indicates whether the menu is currently visible.
	 */
	protected boolean mMenuVisible;

	/**
	 * The drag mode of the drawer. Can be either {@link #MENU_DRAG_CONTENT} or {@link #MENU_DRAG_WINDOW}.
	 */
	private int mDragMode = MENU_DRAG_CONTENT;

	/**
	 * The current drawer state.
	 *
	 * @see #STATE_CLOSED
	 * @see #STATE_CLOSING
	 * @see #STATE_DRAGGING
	 * @see #STATE_OPENING
	 * @see #STATE_OPEN
	 */
	protected int mDrawerState = STATE_CLOSED;

	/**
	 * The touch bezel size of the drawer in px.
	 */
	protected int mTouchBezelSize;

	/**
	 * The touch area size of the drawer in px.
	 */
	protected int mTouchSize;

	/**
	 * Listener used to dispatch state change events.
	 */
	private OnDrawerStateChangeListener mOnDrawerStateChangeListener;

	/**
	 * Touch mode for the Drawer.
	 * Possible values are {@link #TOUCH_MODE_NONE}, {@link #TOUCH_MODE_BEZEL} or {@link #TOUCH_MODE_FULLSCREEN}
	 * Default: {@link #TOUCH_MODE_BEZEL}
	 */
	protected int mTouchMode = TOUCH_MODE_BEZEL;

	/**
	 * Indicates whether to use {@link View#LAYER_TYPE_HARDWARE} when animating the drawer.
	 */
	protected boolean mHardwareLayersEnabled = true;

	/**
	 * The Activity the drawer is attached to.
	 */
	private Activity mActivity;

	/**
	 * Scroller used when animating the indicator to a new position.
	 */
	private FloatScroller mIndicatorScroller;

	/**
	 * Runnable used when animating the indicator to a new position.
	 */
	private Runnable mIndicatorRunnable = new Runnable()
	{
		@Override
		public void run()
		{
			animateIndicatorInvalidate();
		}
	};

	/**
	 * The start position of the indicator when animating it to a new position.
	 */
	protected int mIndicatorStartPos;

	/**
	 * [0..1] value indicating the current progress of the animation.
	 */
	protected float mIndicatorOffset;

	/**
	 * Whether the indicator is currently animating.
	 */
	protected boolean mIndicatorAnimating;

	/**
	 * Bundle used to hold the drawers state.
	 */
	protected Bundle mState;

	/**
	 * The maximum duration of open/close animations.
	 */
	protected int mMaxAnimationDuration = DEFAULT_ANIMATION_DURATION;

	/**
	 * Callback that lets the listener override intercepting of touch events.
	 */
	protected OnInterceptMoveEventListener mOnInterceptMoveEventListener;

	protected SlideDrawable mSlideDrawable;

	protected Drawable mThemeUpIndicator;

	protected boolean mDrawerIndicatorEnabled;

	private ActionBarHelper mActionBarHelper;

	private int mCurrentUpContentDesc;

	private int mDrawerOpenContentDesc;

	private int mDrawerClosedContentDesc;

	/**
	 * The position of the drawer.
	 */
	private Position mPosition;

	private Position mResolvedPosition;

	private final Rect mIndicatorClipRect = new Rect();

	protected boolean mIsStatic;

	protected final Rect mDropShadowRect = new Rect();

	/**
	 * Current offset.
	 */
	protected float mOffsetPixels;

	/**
	 * Whether an overlay should be drawn as the drawer is opened and closed.
	 */
	protected boolean mDrawOverlay;

	/**
	 * Attaches the MenuDrawer to the Activity.
	 *
	 * @param activity The activity that the MenuDrawer will be attached to.
	 * @return The created MenuDrawer instance.
	 */
	public static MenuDrawer attach(Activity activity)
	{
		return attach(activity, Type.BEHIND);
	}

	/**
	 * Attaches the MenuDrawer to the Activity.
	 *
	 * @param activity The activity the menu drawer will be attached to.
	 * @param type     The {@link Type} of the drawer.
	 * @return The created MenuDrawer instance.
	 */
	public static MenuDrawer attach(Activity activity, Type type)
	{
		return attach(activity, type, Position.START);
	}

	/**
	 * Attaches the MenuDrawer to the Activity.
	 *
	 * @param activity The activity the menu drawer will be attached to.
	 * @param position Where to position the menu.
	 * @return The created MenuDrawer instance.
	 */
	public static MenuDrawer attach(Activity activity, Position position)
	{
		return attach(activity, Type.BEHIND, position);
	}

	/**
	 * Attaches the MenuDrawer to the Activity.
	 *
	 * @param activity The activity the menu drawer will be attached to.
	 * @param type     The {@link Type} of the drawer.
	 * @param position Where to position the menu.
	 * @return The created MenuDrawer instance.
	 */
	public static MenuDrawer attach(Activity activity, Type type, Position position)
	{
		return attach(activity, type, position, MENU_DRAG_CONTENT);
	}

	/**
	 * Attaches the MenuDrawer to the Activity.
	 *
	 * @param activity The activity the menu drawer will be attached to.
	 * @param type     The {@link Type} of the drawer.
	 * @param position Where to position the menu.
	 * @param dragMode The drag mode of the drawer. Can be either {@link MenuDrawer#MENU_DRAG_CONTENT}
	 *                 or {@link MenuDrawer#MENU_DRAG_WINDOW}.
	 * @return The created MenuDrawer instance.
	 */
	public static MenuDrawer attach(Activity activity, Type type, Position position, int dragMode)
	{
		MenuDrawer menuDrawer = createMenuDrawer(activity, dragMode, position, type);
		menuDrawer.setId(R.id.md__drawer);

		switch (dragMode)
		{
		case MenuDrawer.MENU_DRAG_CONTENT:
			attachToContent(activity, menuDrawer);
			break;

		case MenuDrawer.MENU_DRAG_WINDOW:
			attachToDecor(activity, menuDrawer);
			break;

		default:
			throw new RuntimeException("Unknown menu mode: " + dragMode);
		}

		return menuDrawer;
	}

	/**
	 * Constructs the appropriate MenuDrawer based on the position.
	 */
	private static MenuDrawer createMenuDrawer(Activity activity, int dragMode, Position position, Type type)
	{
		MenuDrawer drawer;

		if (type == Type.STATIC)
		{
			drawer = new StaticDrawer(activity);

		}
		else if (type == Type.OVERLAY)
		{
			drawer = new OverlayDrawer(activity, dragMode);
			if (position == Position.LEFT || position == Position.START)
			{
				drawer.setupUpIndicator(activity);
			}

		}
		else
		{
			drawer = new SlidingDrawer(activity, dragMode);
			if (position == Position.LEFT || position == Position.START)
			{
				drawer.setupUpIndicator(activity);
			}
		}

		drawer.mDragMode = dragMode;
		drawer.setPosition(position);

		return drawer;
	}

	/**
	 * Attaches the menu drawer to the content view.
	 */
	private static void attachToContent(Activity activity, MenuDrawer menuDrawer)
	{
		/**
		 * Do not call mActivity#setContentView.
		 * E.g. if using with a ListActivity, Activity#setContentView is overridden and dispatched to
		 * MenuDrawer#setContentView, which then again would call Activity#setContentView.
		 */
		ViewGroup content = (ViewGroup) activity.findViewById(android.R.id.content);
		content.removeAllViews();
		content.addView(menuDrawer, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
	}

	/**
	 * Attaches the menu drawer to the window.
	 */
	private static void attachToDecor(Activity activity, MenuDrawer menuDrawer)
	{
		ViewGroup decorView = (ViewGroup) activity.getWindow().getDecorView();
		ViewGroup decorChild = (ViewGroup) decorView.getChildAt(0);

		decorView.removeAllViews();
		decorView.addView(menuDrawer, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);

		menuDrawer.mContentContainer.addView(decorChild, decorChild.getLayoutParams());
	}

	MenuDrawer(Activity activity, int dragMode)
	{
		this(activity);

		mActivity = activity;
		mDragMode = dragMode;
	}

	public MenuDrawer(Context context)
	{
		this(context, null);
	}

	public MenuDrawer(Context context, AttributeSet attrs)
	{
		this(context, attrs, R.attr.menuDrawerStyle);
	}

	public MenuDrawer(Context context, AttributeSet attrs, int defStyle)
	{
		super(context, attrs, defStyle);
		initDrawer(context, attrs, defStyle);
	}

	protected void initDrawer(Context context, AttributeSet attrs, int defStyle)
	{
		setWillNotDraw(false);
		setFocusable(false);

		TypedArray a = context.obtainStyledAttributes(attrs, R.styleable.MenuDrawer, R.attr.menuDrawerStyle, R.style.Widget_MenuDrawer);

		final Drawable contentBackground = a.getDrawable(R.styleable.MenuDrawer_mdContentBackground);
		final Drawable menuBackground = a.getDrawable(R.styleable.MenuDrawer_mdMenuBackground);

		mMenuSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdMenuSize, dpToPx(240));

		final int indicatorResId = a.getResourceId(R.styleable.MenuDrawer_mdActiveIndicator, 0);
		if (indicatorResId != 0)
		{
			mActiveIndicator = BitmapFactory.decodeResource(getResources(), indicatorResId);
		}

		mDropShadowEnabled = a.getBoolean(R.styleable.MenuDrawer_mdDropShadowEnabled, true);

		mDropShadowDrawable = a.getDrawable(R.styleable.MenuDrawer_mdDropShadow);

		if (mDropShadowDrawable == null)
		{
			mDropShadowColor = a.getColor(R.styleable.MenuDrawer_mdDropShadowColor, 0xFF000000);
		}
		else
		{
			mCustomDropShadow = true;
		}

		mDropShadowSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdDropShadowSize, dpToPx(DEFAULT_DROP_SHADOW_DP));

		mTouchBezelSize = a.getDimensionPixelSize(R.styleable.MenuDrawer_mdTouchBezelSize, dpToPx(DEFAULT_DRAG_BEZEL_DP));

		mAllowIndicatorAnimation = a.getBoolean(R.styleable.MenuDrawer_mdAllowIndicatorAnimation, false);

		mMaxAnimationDuration = a.getInt(R.styleable.MenuDrawer_mdMaxAnimationDuration, DEFAULT_ANIMATION_DURATION);

		final int slideDrawableResId = a.getResourceId(R.styleable.MenuDrawer_mdSlideDrawable, -1);
		if (slideDrawableResId != -1)
		{
			setSlideDrawable(slideDrawableResId);
		}

		mDrawerOpenContentDesc = a.getResourceId(R.styleable.MenuDrawer_mdDrawerOpenUpContentDescription, 0);
		mDrawerClosedContentDesc = a.getResourceId(R.styleable.MenuDrawer_mdDrawerClosedUpContentDescription, 0);

		mDrawOverlay = a.getBoolean(R.styleable.MenuDrawer_mdDrawOverlay, true);

		final int position = a.getInt(R.styleable.MenuDrawer_mdPosition, 0);
		setPosition(Position.fromValue(position));

		a.recycle();

		mMenuContainer = new NoClickThroughFrameLayout(context);
		mMenuContainer.setId(R.id.md__menu);
		mMenuContainer.setBackgroundDrawable(menuBackground);

		mContentContainer = new NoClickThroughFrameLayout(context);
		mContentContainer.setId(R.id.md__content);
		mContentContainer.setBackgroundDrawable(contentBackground);

		mMenuOverlay = new ColorDrawable(0xFF000000);

		mIndicatorScroller = new FloatScroller(SMOOTH_INTERPOLATOR);
	}

	@Override
	protected void onFinishInflate()
	{
		super.onFinishInflate();
		View menu = findViewById(R.id.mdMenu);
		if (menu != null)
		{
			removeView(menu);
			setMenuView(menu);
		}

		View content = findViewById(R.id.mdContent);
		if (content != null)
		{
			removeView(content);
			setContentView(content);
		}

		if (getChildCount() > 2)
		{
			throw new IllegalStateException("Menu and content view added in xml must have id's @id/mdMenu and @id/mdContent");
		}
	}

	protected int dpToPx(int dp)
	{
		return (int) (getResources().getDisplayMetrics().density * dp + 0.5f);
	}

	protected boolean isViewDescendant(View v)
	{
		ViewParent parent = v.getParent();
		while (parent != null)
		{
			if (parent == this)
			{
				return true;
			}

			parent = parent.getParent();
		}

		return false;
	}

	@Override
	protected void onAttachedToWindow()
	{
		super.onAttachedToWindow();
		getViewTreeObserver().addOnScrollChangedListener(mScrollListener);
	}

	@Override
	protected void onDetachedFromWindow()
	{
		getViewTreeObserver().removeOnScrollChangedListener(mScrollListener);
		super.onDetachedFromWindow();
	}

	private boolean shouldDrawIndicator()
	{
		return mActiveView != null && mActiveIndicator != null && isViewDescendant(mActiveView);
	}

	@Override
	protected void dispatchDraw(Canvas canvas)
	{
		super.dispatchDraw(canvas);
		final int offsetPixels = (int) mOffsetPixels;

		if (mDrawOverlay && offsetPixels != 0)
		{
			drawOverlay(canvas);
		}
		if (mDropShadowEnabled && (offsetPixels != 0 || mIsStatic))
		{
			drawDropShadow(canvas);
		}
		if (shouldDrawIndicator() && (offsetPixels != 0 || mIsStatic))
		{
			drawIndicator(canvas);
		}
	}

	protected abstract void drawOverlay(Canvas canvas);

	private void drawDropShadow(Canvas canvas)
	{
		// Can't pass the position to the constructor, so wait with loading the drawable until the drop shadow is
		// actually drawn.
		if (mDropShadowDrawable == null)
		{
			setDropShadowColor(mDropShadowColor);
		}

		updateDropShadowRect();
		mDropShadowDrawable.setBounds(mDropShadowRect);
		mDropShadowDrawable.draw(canvas);
	}

	protected void updateDropShadowRect()
	{
		// This updates the rect for the static and sliding drawer. The overlay drawer has its own implementation.
		switch (getPosition())
		{
		case LEFT:
			mDropShadowRect.top = 0;
			mDropShadowRect.bottom = getHeight();
			mDropShadowRect.right = ViewHelper.getLeft(mContentContainer);
			mDropShadowRect.left = mDropShadowRect.right - mDropShadowSize;
			break;

		case TOP:
			mDropShadowRect.left = 0;
			mDropShadowRect.right = getWidth();
			mDropShadowRect.bottom = ViewHelper.getTop(mContentContainer);
			mDropShadowRect.top = mDropShadowRect.bottom - mDropShadowSize;
			break;

		case RIGHT:
			mDropShadowRect.top = 0;
			mDropShadowRect.bottom = getHeight();
			mDropShadowRect.left = ViewHelper.getRight(mContentContainer);
			mDropShadowRect.right = mDropShadowRect.left + mDropShadowSize;
			break;

		case BOTTOM:
			mDropShadowRect.left = 0;
			mDropShadowRect.right = getWidth();
			mDropShadowRect.top = ViewHelper.getBottom(mContentContainer);
			mDropShadowRect.bottom = mDropShadowRect.top + mDropShadowSize;
			break;
		}
	}

	private void drawIndicator(Canvas canvas)
	{
		Integer position = (Integer) mActiveView.getTag(R.id.mdActiveViewPosition);
		final int pos = position == null ? 0 : position;
		if (pos == mActivePosition)
		{
			updateIndicatorClipRect();
			canvas.save();
			canvas.clipRect(mIndicatorClipRect);

			int drawLeft = 0;
			int drawTop = 0;
			switch (getPosition())
			{
			case LEFT:
			case TOP:
				drawLeft = mIndicatorClipRect.left;
				drawTop = mIndicatorClipRect.top;
				break;

			case RIGHT:
				drawLeft = mIndicatorClipRect.right - mActiveIndicator.getWidth();
				drawTop = mIndicatorClipRect.top;
				break;

			case BOTTOM:
				drawLeft = mIndicatorClipRect.left;
				drawTop = mIndicatorClipRect.bottom - mActiveIndicator.getHeight();
			}

			canvas.drawBitmap(mActiveIndicator, drawLeft, drawTop, null);
			canvas.restore();
		}
	}

	/**
	 * Update the {@link Rect} where the indicator is drawn.
	 */
	protected void updateIndicatorClipRect()
	{
		mActiveView.getDrawingRect(mActiveRect);
		offsetDescendantRectToMyCoords(mActiveView, mActiveRect);

		final float openRatio = mIsStatic ? 1.0f : Math.abs(mOffsetPixels) / mMenuSize;

		final float interpolatedRatio = 1.f - INDICATOR_INTERPOLATOR.getInterpolation((1.f - openRatio));

		final int indicatorWidth = mActiveIndicator.getWidth();
		final int indicatorHeight = mActiveIndicator.getHeight();

		final int interpolatedWidth = (int) (indicatorWidth * interpolatedRatio);
		final int interpolatedHeight = (int) (indicatorHeight * interpolatedRatio);

		final int startPos = mIndicatorStartPos;

		int left = 0;
		int top = 0;
		int right = 0;
		int bottom = 0;

		switch (getPosition())
		{
		case LEFT:
		case RIGHT:
			final int finalTop = mActiveRect.top + ((mActiveRect.height() - indicatorHeight) / 2);
			if (mIndicatorAnimating)
			{
				top = (int) (startPos + ((finalTop - startPos) * mIndicatorOffset));
			}
			else
			{
				top = finalTop;
			}
			bottom = top + indicatorHeight;
			break;

		case TOP:
		case BOTTOM:
			final int finalLeft = mActiveRect.left + ((mActiveRect.width() - indicatorWidth) / 2);
			if (mIndicatorAnimating)
			{
				left = (int) (startPos + ((finalLeft - startPos) * mIndicatorOffset));
			}
			else
			{
				left = finalLeft;
			}
			right = left + indicatorWidth;
			break;
		}

		switch (getPosition())
		{
		case LEFT:
		{
			right = ViewHelper.getLeft(mContentContainer);
			left = right - interpolatedWidth;
			break;
		}

		case TOP:
		{
			bottom = ViewHelper.getTop(mContentContainer);
			top = bottom - interpolatedHeight;
			break;
		}

		case RIGHT:
		{
			left = ViewHelper.getRight(mContentContainer);
			right = left + interpolatedWidth;
			break;
		}

		case BOTTOM:
		{
			top = ViewHelper.getBottom(mContentContainer);
			bottom = top + interpolatedHeight;
			break;
		}
		}

		mIndicatorClipRect.left = left;
		mIndicatorClipRect.top = top;
		mIndicatorClipRect.right = right;
		mIndicatorClipRect.bottom = bottom;
	}

	private void setPosition(Position position)
	{
		mPosition = position;
		mResolvedPosition = getPosition();
	}

	protected Position getPosition()
	{
		final int layoutDirection = ViewHelper.getLayoutDirection(this);

		switch (mPosition)
		{
		case START:
			if (layoutDirection == LAYOUT_DIRECTION_RTL)
			{
				return Position.RIGHT;
			}
			else
			{
				return Position.LEFT;
			}

		case END:
			if (layoutDirection == LAYOUT_DIRECTION_RTL)
			{
				return Position.LEFT;
			}
			else
			{
				return Position.RIGHT;
			}
		}

		return mPosition;
	}

	@Override
	public void onRtlPropertiesChanged(int layoutDirection)
	{
		super.onRtlPropertiesChanged(layoutDirection);

		if (!mCustomDropShadow)
			setDropShadowColor(mDropShadowColor);

		if (getPosition() != mResolvedPosition)
		{
			mResolvedPosition = getPosition();
			setOffsetPixels(mOffsetPixels * -1);
		}

		if (mSlideDrawable != null)
			mSlideDrawable.setIsRtl(layoutDirection == LAYOUT_DIRECTION_RTL);

		requestLayout();
		invalidate();
	}

	/**
	 * Sets the number of pixels the content should be offset.
	 *
	 * @param offsetPixels The number of pixels to offset the content by.
	 */
	protected void setOffsetPixels(float offsetPixels)
	{
		final int oldOffset = (int) mOffsetPixels;
		final int newOffset = (int) offsetPixels;

		mOffsetPixels = offsetPixels;

		if (mSlideDrawable != null)
		{
			final float offset = Math.abs(mOffsetPixels) / mMenuSize;
			mSlideDrawable.setOffset(offset);
			updateUpContentDescription();
		}

		if (newOffset != oldOffset)
		{
			onOffsetPixelsChanged(newOffset);
			mMenuVisible = newOffset != 0;

			// Notify any attached listeners of the current open ratio
			final float openRatio = ((float) Math.abs(newOffset)) / mMenuSize;
			dispatchOnDrawerSlide(openRatio, newOffset);
		}
	}

	/**
	 * Called when the number of pixels the content should be offset by has changed.
	 *
	 * @param offsetPixels The number of pixels to offset the content by.
	 */
	protected abstract void onOffsetPixelsChanged(int offsetPixels);

	/**
	 * Toggles the menu open and close with animation.
	 */
	public void toggleMenu()
	{
		toggleMenu(true);
	}

	/**
	 * Toggles the menu open and close.
	 *
	 * @param animate Whether open/close should be animated.
	 */
	public abstract void toggleMenu(boolean animate);

	/**
	 * Animates the menu open.
	 */
	public void openMenu()
	{
		openMenu(true);
	}

	/**
	 * Opens the menu.
	 *
	 * @param animate Whether open/close should be animated.
	 */
	public abstract void openMenu(boolean animate);

	/**
	 * Animates the menu closed.
	 */
	public void closeMenu()
	{
		closeMenu(true);
	}

	/**
	 * Closes the menu.
	 *
	 * @param animate Whether open/close should be animated.
	 */
	public abstract void closeMenu(boolean animate);

	/**
	 * Indicates whether the menu is currently visible.
	 *
	 * @return True if the menu is open, false otherwise.
	 */
	public abstract boolean isMenuVisible();

	/**
	 * Set the size of the menu drawer when open.
	 *
	 * @param size The size of the menu.
	 */
	public abstract void setMenuSize(int size);

	/**
	 * Returns the size of the menu.
	 *
	 * @return The size of the menu.
	 */
	public int getMenuSize()
	{
		return mMenuSize;
	}

	/**
	 * Set the active view.
	 * If the mdActiveIndicator attribute is set, this View will have the indicator drawn next to it.
	 *
	 * @param v The active view.
	 */
	public void setActiveView(View v)
	{
		setActiveView(v, 0);
	}

	/**
	 * Set the active view.
	 * If the mdActiveIndicator attribute is set, this View will have the indicator drawn next to it.
	 *
	 * @param v        The active view.
	 * @param position Optional position, usually used with ListView. v.setTag(R.id.mdActiveViewPosition, position)
	 *                 must be called first.
	 */
	public void setActiveView(View v, int position)
	{
		final View oldView = mActiveView;
		mActiveView = v;
		mActivePosition = position;

		if (mAllowIndicatorAnimation && oldView != null)
		{
			startAnimatingIndicator();
		}

		invalidate();
	}

	/**
	 * Sets whether the indicator should be animated between active views.
	 *
	 * @param animate Whether the indicator should be animated between active views.
	 */
	public void setAllowIndicatorAnimation(boolean animate)
	{
		if (animate != mAllowIndicatorAnimation)
		{
			mAllowIndicatorAnimation = animate;
			completeAnimatingIndicator();
		}
	}

	/**
	 * Indicates whether the indicator should be animated between active views.
	 *
	 * @return Whether the indicator should be animated between active views.
	 */
	public boolean getAllowIndicatorAnimation()
	{
		return mAllowIndicatorAnimation;
	}

	/**
	 * Scroll listener that checks whether the active view has moved before the drawer is invalidated.
	 */
	private ViewTreeObserver.OnScrollChangedListener mScrollListener = new ViewTreeObserver.OnScrollChangedListener()
	{
		@Override
		public void onScrollChanged()
		{
			if (mActiveView != null && isViewDescendant(mActiveView))
			{
				mActiveView.getDrawingRect(mTempRect);
				offsetDescendantRectToMyCoords(mActiveView, mTempRect);
				if (mTempRect.left != mActiveRect.left || mTempRect.top != mActiveRect.top || mTempRect.right != mActiveRect.right
				        || mTempRect.bottom != mActiveRect.bottom)
				{
					invalidate();
				}
			}
		}
	};

	/**
	 * Starts animating the indicator to a new position.
	 */
	private void startAnimatingIndicator()
	{
		mIndicatorStartPos = getIndicatorStartPos();
		mIndicatorAnimating = true;
		mIndicatorScroller.startScroll(0.0f, 1.0f, INDICATOR_ANIM_DURATION);

		animateIndicatorInvalidate();
	}

	/**
	 * Returns the start position of the indicator.
	 *
	 * @return The start position of the indicator.
	 */
	private int getIndicatorStartPos()
	{
		switch (getPosition())
		{
		case TOP:
			return mIndicatorClipRect.left;
		case RIGHT:
			return mIndicatorClipRect.top;
		case BOTTOM:
			return mIndicatorClipRect.left;
		default:
			return mIndicatorClipRect.top;
		}
	}

	/**
	 * Compute the touch area based on the touch mode.
	 */
	protected void updateTouchAreaSize()
	{
		if (mTouchMode == TOUCH_MODE_BEZEL)
		{
			mTouchSize = mTouchBezelSize;
		}
		else if (mTouchMode == TOUCH_MODE_FULLSCREEN)
		{
			mTouchSize = getMeasuredWidth();
		}
		else
		{
			mTouchSize = 0;
		}
	}

	/**
	 * Callback when each frame in the indicator animation should be drawn.
	 */
	private void animateIndicatorInvalidate()
	{
		if (mIndicatorScroller.computeScrollOffset())
		{
			mIndicatorOffset = mIndicatorScroller.getCurr();
			invalidate();

			if (!mIndicatorScroller.isFinished())
			{
				postOnAnimation(mIndicatorRunnable);
				return;
			}
		}

		completeAnimatingIndicator();
	}

	/**
	 * Called when the indicator animation has completed.
	 */
	private void completeAnimatingIndicator()
	{
		mIndicatorOffset = 1.0f;
		mIndicatorAnimating = false;
		invalidate();
	}

	/**
	 * Enables or disables offsetting the menu when dragging the drawer.
	 *
	 * @param offsetMenu True to offset the menu, false otherwise.
	 */
	public abstract void setOffsetMenuEnabled(boolean offsetMenu);

	/**
	 * Indicates whether the menu is being offset when dragging the drawer.
	 *
	 * @return True if the menu is being offset, false otherwise.
	 */
	public abstract boolean getOffsetMenuEnabled();

	/**
	 * Get the current state of the drawer.
	 *
	 * @return The state of the drawer.
	 */
	public int getDrawerState()
	{
		return mDrawerState;
	}

	/**
	 * Register a callback to be invoked when the drawer state changes.
	 *
	 * @param listener The callback that will run.
	 */
	public void setOnDrawerStateChangeListener(OnDrawerStateChangeListener listener)
	{
		mOnDrawerStateChangeListener = listener;
	}

	/**
	 * Register a callback that will be invoked when the drawer is about to intercept touch events.
	 *
	 * @param listener The callback that will be invoked.
	 */
	public void setOnInterceptMoveEventListener(OnInterceptMoveEventListener listener)
	{
		mOnInterceptMoveEventListener = listener;
	}

	/**
	 * Defines whether the drop shadow is enabled.
	 *
	 * @param enabled Whether the drop shadow is enabled.
	 */
	public void setDropShadowEnabled(boolean enabled)
	{
		mDropShadowEnabled = enabled;
		invalidate();
	}

	protected GradientDrawable.Orientation getDropShadowOrientation()
	{
		// Gets the orientation for the static and sliding drawer. The overlay drawer provides its own implementation.
		switch (getPosition())
		{
		case TOP:
			return GradientDrawable.Orientation.BOTTOM_TOP;

		case RIGHT:
			return GradientDrawable.Orientation.LEFT_RIGHT;

		case BOTTOM:
			return GradientDrawable.Orientation.TOP_BOTTOM;

		default:
			return GradientDrawable.Orientation.RIGHT_LEFT;
		}
	}

	/**
	 * Sets the color of the drop shadow.
	 *
	 * @param color The color of the drop shadow.
	 */
	public void setDropShadowColor(int color)
	{
		GradientDrawable.Orientation orientation = getDropShadowOrientation();

		final int endColor = color & 0x00FFFFFF;
		mDropShadowDrawable = new GradientDrawable(orientation, new int[] { color, endColor, });
		invalidate();
	}

	/**
	 * Sets the drawable of the drop shadow.
	 *
	 * @param drawable The drawable of the drop shadow.
	 */
	public void setDropShadow(Drawable drawable)
	{
		mDropShadowDrawable = drawable;
		mCustomDropShadow = drawable != null;
		invalidate();
	}

	/**
	 * Sets the drawable of the drop shadow.
	 *
	 * @param resId The resource identifier of the the drawable.
	 */
	public void setDropShadow(int resId)
	{
		setDropShadow(getResources().getDrawable(resId));
	}

	/**
	 * Returns the drawable of the drop shadow.
	 */
	public Drawable getDropShadow()
	{
		return mDropShadowDrawable;
	}

	/**
	 * Sets the size of the drop shadow.
	 *
	 * @param size The size of the drop shadow in px.
	 */
	public void setDropShadowSize(int size)
	{
		mDropShadowSize = size;
		invalidate();
	}

	/**
	 * Animates the drawer slightly open until the user opens the drawer.
	 */
	public abstract void peekDrawer();

	/**
	 * Animates the drawer slightly open. If delay is larger than 0, this happens until the user opens the drawer.
	 *
	 * @param delay The delay (in milliseconds) between each run of the animation. If 0, this animation is only run
	 *              once.
	 */
	public abstract void peekDrawer(long delay);

	/**
	 * Animates the drawer slightly open. If delay is larger than 0, this happens until the user opens the drawer.
	 *
	 * @param startDelay The delay (in milliseconds) until the animation is first run.
	 * @param delay      The delay (in milliseconds) between each run of the animation. If 0, this animation is only run
	 *                   once.
	 */
	public abstract void peekDrawer(long startDelay, long delay);

	/**
	 * Enables or disables the user of {@link View#LAYER_TYPE_HARDWARE} when animations views.
	 *
	 * @param enabled Whether hardware layers are enabled.
	 */
	public abstract void setHardwareLayerEnabled(boolean enabled);

	/**
	 * Sets the maximum duration of open/close animations.
	 *
	 * @param duration The maximum duration in milliseconds.
	 */
	public void setMaxAnimationDuration(int duration)
	{
		mMaxAnimationDuration = duration;
	}

	/**
	 * Sets whether an overlay should be drawn when sliding the drawer.
	 *
	 * @param drawOverlay Whether an overlay should be drawn when sliding the drawer.
	 */
	public void setDrawOverlay(boolean drawOverlay)
	{
		mDrawOverlay = drawOverlay;
	}

	/**
	 * Gets whether an overlay is drawn when sliding the drawer.
	 *
	 * @return Whether an overlay is drawn when sliding the drawer.
	 */
	public boolean getDrawOverlay()
	{
		return mDrawOverlay;
	}

	protected void updateUpContentDescription()
	{
		final int upContentDesc = isMenuVisible() ? mDrawerOpenContentDesc : mDrawerClosedContentDesc;
		if (mDrawerIndicatorEnabled && mActionBarHelper != null && upContentDesc != mCurrentUpContentDesc)
		{
			mCurrentUpContentDesc = upContentDesc;
			mActionBarHelper.setActionBarDescription(upContentDesc);
		}
	}

	/**
	 * Sets the drawable used as the drawer indicator.
	 *
	 * @param drawable The drawable used as the drawer indicator.
	 */
	public void setSlideDrawable(int drawableRes)
	{
		setSlideDrawable(getResources().getDrawable(drawableRes));
	}

	/**
	 * Sets the drawable used as the drawer indicator.
	 *
	 * @param drawable The drawable used as the drawer indicator.
	 */
	public void setSlideDrawable(Drawable drawable)
	{
		mSlideDrawable = new SlideDrawable(drawable);
		mSlideDrawable.setIsRtl(ViewHelper.getLayoutDirection(this) == LAYOUT_DIRECTION_RTL);

		if (mActionBarHelper != null)
		{
			mActionBarHelper.setDisplayShowHomeAsUpEnabled(true);

			if (mDrawerIndicatorEnabled)
			{
				mActionBarHelper.setActionBarUpIndicator(mSlideDrawable, isMenuVisible() ? mDrawerOpenContentDesc : mDrawerClosedContentDesc);
			}
		}
	}

	/**
	 * Sets up the drawer indicator. It cna then be shown with {@link #setDrawerIndicatorEnabled(boolean)}.
	 *
	 * @param activity The activity the drawer is attached to.
	 */
	public void setupUpIndicator(Activity activity)
	{
		if (mActionBarHelper == null)
		{
			mActionBarHelper = new ActionBarHelper(activity);
			mThemeUpIndicator = mActionBarHelper.getThemeUpIndicator();

			if (mDrawerIndicatorEnabled)
			{
				mActionBarHelper.setActionBarUpIndicator(mSlideDrawable, isMenuVisible() ? mDrawerOpenContentDesc : mDrawerClosedContentDesc);
			}
		}
	}

	/**
	 * Sets whether the drawer indicator should be enabled. {@link #setupUpIndicator(android.app.Activity)} must be
	 * called first.
	 *
	 * @param enabled Whether the drawer indicator should enabled.
	 */
	public void setDrawerIndicatorEnabled(boolean enabled)
	{
		if (mActionBarHelper == null)
		{
			throw new IllegalStateException("setupUpIndicator(Activity) has not been called");
		}

		mDrawerIndicatorEnabled = enabled;
		if (enabled)
		{
			mActionBarHelper.setActionBarUpIndicator(mSlideDrawable, isMenuVisible() ? mDrawerOpenContentDesc : mDrawerClosedContentDesc);
		}
		else
		{
			mActionBarHelper.setActionBarUpIndicator(mThemeUpIndicator, 0);
		}
	}

	/**
	 * Indicates whether the drawer indicator is currently enabled.
	 *
	 * @return Whether the drawer indicator is enabled.
	 */
	public boolean isDrawerIndicatorEnabled()
	{
		return mDrawerIndicatorEnabled;
	}

	/**
	 * Returns the ViewGroup used as a parent for the menu view.
	 *
	 * @return The menu view's parent.
	 */
	public ViewGroup getMenuContainer()
	{
		return mMenuContainer;
	}

	/**
	 * Returns the ViewGroup used as a parent for the content view.
	 *
	 * @return The content view's parent.
	 */
	public ViewGroup getContentContainer()
	{
		if (mDragMode == MENU_DRAG_CONTENT)
		{
			return mContentContainer;
		}
		else
		{
			return (ViewGroup) findViewById(android.R.id.content);
		}
	}

	/**
	 * Set the menu view from a layout resource.
	 *
	 * @param layoutResId Resource ID to be inflated.
	 */
	public void setMenuView(int layoutResId)
	{
		mMenuContainer.removeAllViews();
		mMenuView = LayoutInflater.from(getContext()).inflate(layoutResId, mMenuContainer, false);
		mMenuContainer.addView(mMenuView);
	}

	/**
	 * Set the menu view to an explicit view.
	 *
	 * @param view The menu view.
	 */
	public void setMenuView(View view)
	{
		setMenuView(view, new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
	}

	/**
	 * Set the menu view to an explicit view.
	 *
	 * @param view   The menu view.
	 * @param params Layout parameters for the view.
	 */
	public void setMenuView(View view, LayoutParams params)
	{
		mMenuView = view;
		mMenuContainer.removeAllViews();
		mMenuContainer.addView(view, params);
	}

	/**
	 * Returns the menu view.
	 *
	 * @return The menu view.
	 */
	public View getMenuView()
	{
		return mMenuView;
	}

	/**
	 * Set the content from a layout resource.
	 *
	 * @param layoutResId Resource ID to be inflated.
	 */
	public void setContentView(int layoutResId)
	{
		switch (mDragMode)
		{
		case MenuDrawer.MENU_DRAG_CONTENT:
			mContentContainer.removeAllViews();
			LayoutInflater.from(getContext()).inflate(layoutResId, mContentContainer, true);
			break;

		case MenuDrawer.MENU_DRAG_WINDOW:
			mActivity.setContentView(layoutResId);
			break;
		}
	}

	/**
	 * Set the content to an explicit view.
	 *
	 * @param view The desired content to display.
	 */
	public void setContentView(View view)
	{
		setContentView(view, new ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
	}

	/**
	 * Set the content to an explicit view.
	 *
	 * @param view   The desired content to display.
	 * @param params Layout parameters for the view.
	 */
	public void setContentView(View view, LayoutParams params)
	{
		switch (mDragMode)
		{
		case MenuDrawer.MENU_DRAG_CONTENT:
			mContentContainer.removeAllViews();
			mContentContainer.addView(view, params);
			break;

		case MenuDrawer.MENU_DRAG_WINDOW:
			mActivity.setContentView(view, params);
			break;
		}
	}

	protected void setDrawerState(int state)
	{
		if (state != mDrawerState)
		{
			final int oldState = mDrawerState;
			mDrawerState = state;
			if (mOnDrawerStateChangeListener != null)
				mOnDrawerStateChangeListener.onDrawerStateChange(oldState, state);
			if (DEBUG)
				logDrawerState(state);
		}
	}

	protected void logDrawerState(int state)
	{
		switch (state)
		{
		case STATE_CLOSED:
			Log.d(TAG, "[DrawerState] STATE_CLOSED");
			break;

		case STATE_CLOSING:
			Log.d(TAG, "[DrawerState] STATE_CLOSING");
			break;

		case STATE_DRAGGING:
			Log.d(TAG, "[DrawerState] STATE_DRAGGING");
			break;

		case STATE_OPENING:
			Log.d(TAG, "[DrawerState] STATE_OPENING");
			break;

		case STATE_OPEN:
			Log.d(TAG, "[DrawerState] STATE_OPEN");
			break;

		default:
			Log.d(TAG, "[DrawerState] Unknown: " + state);
		}
	}

	/**
	 * Returns the touch mode.
	 */
	public abstract int getTouchMode();

	/**
	 * Sets the drawer touch mode. Possible values are {@link #TOUCH_MODE_NONE}, {@link #TOUCH_MODE_BEZEL} or
	 * {@link #TOUCH_MODE_FULLSCREEN}.
	 *
	 * @param mode The touch mode.
	 */
	public abstract void setTouchMode(int mode);

	/**
	 * Sets the size of the touch bezel.
	 *
	 * @param size The touch bezel size in px.
	 */
	public abstract void setTouchBezelSize(int size);

	/**
	 * Returns the size of the touch bezel in px.
	 */
	public abstract int getTouchBezelSize();

	@Override
	public void postOnAnimation(Runnable action)
	{
		if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN)
		{
			super.postOnAnimation(action);
		}
		else
		{
			postDelayed(action, ANIMATION_DELAY);
		}
	}

	@Override
	protected boolean fitSystemWindows(Rect insets)
	{
		if (mDragMode == MENU_DRAG_WINDOW && mPosition != Position.BOTTOM)
		{
			mMenuContainer.setPadding(0, insets.top, 0, 0);
		}
		return super.fitSystemWindows(insets);
	}

	protected void dispatchOnDrawerSlide(float openRatio, int offsetPixels)
	{
		if (mOnDrawerStateChangeListener != null)
		{
			mOnDrawerStateChangeListener.onDrawerSlide(openRatio, offsetPixels);
		}
	}

	/**
	 * Saves the state of the drawer.
	 *
	 * @return Returns a Parcelable containing the drawer state.
	 */
	public final Parcelable saveState()
	{
		if (mState == null)
			mState = new Bundle();
		saveState(mState);
		return mState;
	}

	void saveState(Bundle state)
	{
		// State saving isn't required for subclasses.
	}

	/**
	 * Restores the state of the drawer.
	 *
	 * @param in A parcelable containing the drawer state.
	 */
	public void restoreState(Parcelable in)
	{
		mState = (Bundle) in;
	}

	@Override
	protected Parcelable onSaveInstanceState()
	{
		Parcelable superState = super.onSaveInstanceState();
		SavedState state = new SavedState(superState);

		if (mState == null)
			mState = new Bundle();
		saveState(mState);

		state.mState = mState;
		return state;
	}

	@Override
	protected void onRestoreInstanceState(Parcelable state)
	{
		SavedState savedState = (SavedState) state;
		super.onRestoreInstanceState(savedState.getSuperState());

		restoreState(savedState.mState);
	}

	static class SavedState extends BaseSavedState
	{

		Bundle mState;

		public SavedState(Parcelable superState)
		{
			super(superState);
		}

		public SavedState(Parcel in)
		{
			super(in);
			mState = in.readBundle();
		}

		@Override
		public void writeToParcel(Parcel dest, int flags)
		{
			super.writeToParcel(dest, flags);
			dest.writeBundle(mState);
		}

		@SuppressWarnings("UnusedDeclaration")
		public static final Creator<SavedState> CREATOR = new Creator<SavedState>()
		{
			@Override
			public SavedState createFromParcel(Parcel in)
			{
				return new SavedState(in);
			}

			@Override
			public SavedState[] newArray(int size)
			{
				return new SavedState[size];
			}
		};
	}
}
